/**
 * 
 */
package com.rlevin.facade.logging;

import java.util.logging.*;
import com.rlevin.facade.logging.LogLevel;

interface ITestableLogger {
	public void addCustomHandlerForUnitTesting();
}

interface ILoggingFacade extends ITestableLogger {
	public void setHandler(Handler handler);
	public void setLevel(int level);
	public int getLevel();
	public void logNormal (String message);	
	public void logVerbose(String message);
	public void logWarning(String message);
	// For unit testing
	public String getMessagesLogged();	
	public void clearMessagesLogged();
}


public class LoggingFacade implements ILoggingFacade {
	
	private static Logger logger;
	private static int currentLevel;
	private static boolean customHandlerCreated;
	private static StringBuilder loggedMessages;
	
	/**
	 * Private constructor. Gets called by the factory. Note that this is NOT a
	 * singleton, so each time getInstance is called, we will create a new
	 * LoggingFacade instance. Otherwise, clients would create new logging
	 * facades and still have old data in the loggedMessages, etc.,etc.
	 * @param level The LogLevel to initialize this instance with. @see LogLevel.
	 */
	private LoggingFacade(int level, String name, Handler handler) {
		LoggingFacade.currentLevel = level;
		LoggingFacade.customHandlerCreated = false;
		LoggingFacade.loggedMessages = new StringBuilder();
		logger = Logger.getLogger(name);
		if(null != handler)
			logger.addHandler(handler);
	}
	
	/**
	 * Factory that returns a logger facade at the Log Level passed in. 
	 * 
	 * @param level Don't use an int, use:
	 * 		LogLevel.NORMAL
	 * 		LogLevel.OFF
	 *		LogLevel.WARNING
	 * 		LogLevel.VERBOSE
	 * @param output TODO: Need to add the output destination 
	 * 
	 * @return LoggingFacade instance.
	 */
	public static LoggingFacade getInstance(int level, String name) {
		return new LoggingFacade(level,name, null);
	}
	
	/**
	 * Factory that returns a logger facade at the Log Level passed in,
	 * and uses the Handler passed in. 
	 * 
	 * @param level Don't use an int, use:
	 * 		LogLevel.NORMAL
	 * 		LogLevel.OFF
	 *		LogLevel.WARNING
	 * 		LogLevel.VERBOSE
	 * @param output TODO: Need to add the output destination 
	 * 
	 * @return LoggingFacade instance.
	 */
	public static LoggingFacade getInstance(int level, String name, Handler handler) {
		return new LoggingFacade(level,name,handler);
	}
	
	public int getLevel() {
		return LoggingFacade.currentLevel;
	}

	public void setLevel(int level) {
		LoggingFacade.currentLevel = level;
	}
	
	/**
	 * Logs the message if level is not LogLevel.OFF
	 */
	public void logNormal(String message) {
		this.logAMessage(message,LogLevel.NORMAL);
	}
	
	/**
	 * Logs the message if the level is not LogLevel.OFF, LogLevel.NORMAL, LogLevel.WARNING.
	 * Put differently, will only log if LogLevel.VERBOSE is the current level.
	 */
	public void logVerbose(String message) {
		this.logAMessage(message,LogLevel.VERBOSE);

	}
	
	/**
	 * Logs the message if the level is not LogLevel.OFF, LogLevel.NORMAL.
	 * Put differently, will only log if LogLevel.VERBOSE or LogLevel.WARNING
	 * is the current level.
	 */
	public void logWarning(String message) {
		this.logAMessage(message,LogLevel.WARNING);
	}
	
	// Helper method to do the actual logging if filter methods (logWarning, etc.),
	// decide to call it. I've decided to implement the filtering myself. The reason
	// is that it seems that configuring the global logging properties file can be
	// a real pain, and sometimes the java.util.logging levels don't work as expected.
	// For example, it seems that certain 'global' log levels are set by default, and
	// take a higher priority than the setLevel runtime that the programmer sets. Um,
	// this is a facade, so this I believe helps to 'simplify' using logging ;)
	private void logAMessage(String message, int logLevelCondition) {
		switch (logLevelCondition) {
			case LogLevel.OFF:
				break;
			case LogLevel.NORMAL:
				if(LoggingFacade.currentLevel >= LogLevel.NORMAL)
					LoggingFacade.logger.severe(message);
				break;
			case LogLevel.WARNING:
				if(LoggingFacade.currentLevel >= LogLevel.WARNING)
					LoggingFacade.logger.severe(message);				
				break;
			case LogLevel.VERBOSE:
				LoggingFacade.logger.severe(message);//VERBOSE is as high as we get so no conditional needed ;)	
				break;
			default:
				throw new IllegalArgumentException("Illegal Log Level passed into logAMessage. @see LogLevel for valid levels");
		}
	}

	public void setHandler(Handler handler) {
		LoggingFacade.logger.addHandler(handler);
	}
	// For unit testing the log is working properly.
	public void addCustomHandlerForUnitTesting() {
		LoggingFacade.logger.addHandler(new Handler() {
			public void publish(LogRecord logRecord) {
				LoggingFacade.loggedMessages.append(logRecord.getMessage());
			}

			public void flush() {
			}

			public void close() {
			}
			
			public String toString() {
				return "Custom Handler!";
			}
		});
		LoggingFacade.customHandlerCreated = true;
		/*
		Handler[] handlers = LoggingFacade.logger.getHandlers();
		for(Handler h : handlers) {
			System.out.println("Handler: " + h.toString());
		}
		*/
	}
	
	public String getMessagesLogged() {
		if(LoggingFacade.customHandlerCreated) {	
			return LoggingFacade.loggedMessages.toString();
		} else {
			throw new CustomHandlerForUnitTestingNotCalledFirstException();
		}
	}
	
	public void clearMessagesLogged() {
		if(LoggingFacade.customHandlerCreated) { 
			LoggingFacade.loggedMessages = new StringBuilder();
		} else {
			throw new CustomHandlerForUnitTestingNotCalledFirstException();
		}
	}
}